using System.Windows.Forms;
using OhioState.Math;

namespace OhioState.Controls
{
	/// <summary>
	/// A set of shared control object that provides rotations, etc. based
	/// on mouse events. These are then published as atomic events using 
    /// the IManipulator interface.
	/// </summary>
	/// 
	public class CameraControl
	{
		/// Declare a few properties that control the mouse sensitivity.
		/// 
		private float rotationScale = 1.0f;
		private float zoomScale = 0.008f;
		private float panScale = 0.01f;
        /// <summary>
        /// Get or set the mouse sensitivity for zooming operations.
        /// </summary>
		public float ZoomScale
		{
			get{ return zoomScale; }
			set{ zoomScale = ZoomScale;}
		}
        /// <summary>
        /// Get or set the mouse sensitivity for rotation operations.
        /// </summary>
		public float RotationScale
		{
			get{ return rotationScale; }
			set{ rotationScale = RotationScale; }
		}
        /// <summary>
        /// Get or set the mouse sensitivity for translation operations.
        /// </summary>
		public float PanScale
		{
			get{ return panScale; }
			set{ panScale = PanScale; }
		}
			
		//
		// Add a delegate and event to handle rotations.
		//
		public delegate void Rotate( float angle, Vector3 axis );
		public delegate void Pan( float deltaX, float deltaY, float deltaZ );
		public delegate void Zoom( float zoom_factor );
		// 
		// Add events to control the camera.
		//
		public event Rotate rotate;
		public event Pan pan;
		public event Zoom zoom;
		private Vector3 lastMousePoint;
		private float zoomFactor = 1.0f;

        #region Constructors
        /// <summary>
        /// Default constructor
        /// </summary>
		public CameraControl()
		{
            lastMousePoint = new Vector3(1.0f, 0.0f, 0.0f);
        }
        #endregion

        #region Public methods
        /// <summary>
        /// Initializes the mouse position.
        /// </summary>
        /// <param name="ix">The pixel coordinate in the x-axis.</param>
        /// <param name="iy">The pixel coordinate in the y-axis.</param>
        public void SetMousePosition( int ix, int iy )
        {
            lastMousePoint.x = ix;
            lastMousePoint.y = iy;
        }
        #endregion

        #region Private or protected methods.
        protected Vector3 trackBallMapping(float x, float y)
		{
			//
			// Utility routine to calculate the 3D position of a 
			// projectd unit vector onto the xy-plane. Given any
			// point on the xy-plane, we can think of it as the projection
			// from a sphere down onto the plane. The inverse is what we
			// are after.
			//
            Vector3 v = new Vector3(x, y, 0.0f);
			float d = v.Length();
			//
			// Angel's textbook also has this as: 
			//    v.z = sqrt( 1.0 - ((d < 1.0) ? d : 1.0) );
			//
			v.z = (float) System.Math.Cos( (System.Math.PI / 2.0) * ((d < 1.0) ? d : 1.0) );
			v.normalize();

			return v;
        }
        #endregion
		//
		// Camera provides a few interaction styles that can be attached as callbacks
		//   or events. Typically, this is attached to the mouseMove event of your 
		//   form or control. These are OpenGL or Direct3D independent. They simply 
		//   fire off new events to any forms (or views) listening or subscribed to this
		//   camera.
        //
        #region Controller mouse event handlers.
        //
		// Trackball implements a gimble rotation where we think of the mouse movement
		//   as not planar, but grabbing and spinning a virtual sphere. Any mouse
		//   movements within the projected sphere are mapped to the sphere's surface
		//   and rotation happens along the great circle between any two mouse movements.
		//   Mouse movements outside the projected sphere spin the scene about the viewing
		//   axis.
		// This uses the property RotationScale to control the sensitivity of the mouse
		//   events.
		//
		// ToDo: We would like to use this for many devices, not just a mouse, change
		//   its reuse to accomodate this.
		//
		public void Trackball( object sender, System.Windows.Forms.MouseEventArgs e )
		{
			// 
			// Map the movement of the mouse onto a virtual sphere.
			//
			float x = 2*e.X - ((System.Windows.Forms.Control)sender).Width;
			x = x / (float) ((System.Windows.Forms.Control)sender).Width; // map to -1 to 1.
			float y = ((System.Windows.Forms.Control)sender).Height - 2*e.Y;
			y = y / (float) ((System.Windows.Forms.Control)sender).Height; // map to -1 to 1.
			Vector3 curPoint = trackBallMapping( x, y );
			//
			// Map the last point as well to get the great circle.
			//
			x = 2*lastMousePoint.x - ((System.Windows.Forms.Control)sender).Width;
			x = x / (float) ((System.Windows.Forms.Control)sender).Width; // map to -1 to 1.
			y = ((System.Windows.Forms.Control)sender).Height - 2*lastMousePoint.y;
			y = y / (float) ((System.Windows.Forms.Control)sender).Height; // map to -1 to 1.
            Vector3 lastPoint = trackBallMapping(x, y);
			//
			// Determine the direction and the velocity
			//
            Vector3 direction = curPoint - lastPoint;
			float velocity = direction.Length();
			if( velocity > 0.0001f )
			{
				//
				// Rotate about the axis that is perpendicular to the great circle connecting the mouse movements.
				//
                Vector3 rotAxis = lastPoint.cross(curPoint);
				//
				// The rotation angle would be the angle between lastPoint and curPoint.
				//   This could be calculated as the arcsin( rotAxis.Length() ), but we already
				//   have the distance between the two points and this works out quite well and
				//   smooth, without the additional costs.
				//
				float rotAngle = velocity * rotationScale;
				//
				// Trigger the rotate event (this will be changed to a modelview event)
				//
				if( rotate != null )
				{
					rotate( rotAngle, rotAxis );
				}
				//
				// Save the location of the current point for the next movement. 
				//
				lastMousePoint.x = (float) e.X;
				lastMousePoint.y = (float) e.Y;
			}
		}
		//
		// This uses the property RotationScale to control the sensitivity of the mouse
		//   events.
		//
		// ToDo: We would like to use this for many devices, not just a mouse, change
        //   its reuse to accomodate this. 
		//
		public void RotateXAxis( object sender, System.Windows.Forms.MouseEventArgs e )
		{
			float x = 2*e.X - ((System.Windows.Forms.Control)sender).Width;
			x = x / (float) ((System.Windows.Forms.Control)sender).Width; // map to -1 to 1.
            Vector3 curPoint = new Vector3(x, 0.0f, 0.0f);
			//
			// Map the last point as well
			//
			x = 2*lastMousePoint.x - ((System.Windows.Forms.Control)sender).Width;
			x = x / (float) ((System.Windows.Forms.Control)sender).Width; // map to -1 to 1.
            Vector3 lastPoint = new Vector3(x, 0.0f, 0.0f);
			//
			// Determine the velocity
			//
			float velocity = curPoint.x - lastPoint.x;
			{
				//
				// Rotate about the axis that is perpendicular to the great circle connecting the mouse movements.
				//
                Vector3 rotAxis = new Vector3(0.0f, 1.0f, 0.0f);
				//
				// The rotation angle would be the angle between lastPoint and curPoint.
				//   This could be calculated as the arcsin( rotAxis.Length() ), but we already
				//   have the distance between the two points and this works out quite well and
				//   smooth, without the additional costs.
				//
				float rot_angle = velocity * rotationScale;
				//
				// Trigger the rotate event (this will be changed to a modelview event)
				//
				if( rotate != null )
				{
					rotate( rot_angle, rotAxis );
				}
				//
				// Save the location of the current point for the next movement. 
				//
				lastMousePoint.x = (float) e.X;
				lastMousePoint.y = (float) e.Y;
			}
		}
		//
		// This uses the property RotationScale to control the sensitivity of the mouse
		//   events.
		//
		// ToDo: We would like to use this for many devices, not just a mouse, change
        //   its reuse to accomodate this. 
		//
		public void RotateYAxis( object sender, System.Windows.Forms.MouseEventArgs e )
		{
			float y = 2*e.Y - ((System.Windows.Forms.Control)sender).Height;
			y = y / (float) ((System.Windows.Forms.Control)sender).Height; // map to -1 to 1.
            Vector3 curPoint = new Vector3(0.0f, y, 0.0f);
			//
			// Map the last point as well
			//
			y = 2*lastMousePoint.y - ((System.Windows.Forms.Control)sender).Height;
			y = y / (float) ((System.Windows.Forms.Control)sender).Height; // map to -1 to 1.
            Vector3 lastPoint = new Vector3(0.0f, y, 0.0f);
			//
			// Determine the velocity
			//
			float velocity = curPoint.y - lastPoint.y;
			{
				//
				// Rotate about the axis that is perpendicular to the great circle connecting the mouse movements.
				//
                Vector3 rotAxis = new Vector3(1.0f, 0.0f, 0.0f);
				//
				// The rotation angle would be the angle between lastPoint and curPoint.
				//   This could be calculated as the arcsin( rotAxis.Length() ), but we already
				//   have the distance between the two points and this works out quite well and
				//   smooth, without the additional costs.
				//
				float rot_angle = velocity * rotationScale;
				//
				// Trigger the rotate event (this will be changed to a modelview event)
				//
				if( rotate != null )
				{
					rotate( rot_angle, rotAxis );
				}
				//
				// Save the location of the current point for the next movement. 
				//
				lastMousePoint.x = (float) e.X;
				lastMousePoint.y = (float) e.Y;
			}
		}
		//
		// Translate provides a 2D pan operation parallel to the view plane.
		// It uses the property PanScale to control the mouse sensitivity.
		//
		public void Translate( object sender, System.Windows.Forms.MouseEventArgs e )
		{
            Vector3 direction = new Vector3(e.X - lastMousePoint.x, lastMousePoint.y - e.Y, 0.0f);
			float distance = direction.Length();
			if( distance > 1 )
			{
				if( (System.Windows.Forms.Control.ModifierKeys & Keys.Shift) == Keys.Shift )
				{
					//
					// Constrained translatation
					//   This won't work. We need to remember the mouse down point
					//   and undo any prior work if the user moves from a primarily
					//   horizontal direction to a vertical direction (or even finer
					//   grained angle constraints.
					//
				}
				//
				// Trigger the pan event (this will be changed to a modelview event)
				//
				if( pan != null )
				{
                    if ((System.Windows.Forms.Control.ModifierKeys & Keys.Shift) == Keys.Shift)
                        pan(0, 0, panScale * direction.y);
                    else
                        pan(panScale * direction.x, panScale * direction.y, 0.0f);
				}
				//
				// Save the location of the current point for the next movement. 
				//
				lastMousePoint.x = (float) e.X;
				lastMousePoint.y = (float) e.Y;
			}
		}
	
		//
		// Zoom into or away from the scene based upon how far the mouse moved in the x-direction.
		//   This implementation does this by scaling the eye-space. This is equivalent to
		//   reducing the viewport or the perspective angle.
		//   1. Calculate the signed distance
		//       a. movement to the left is negative (zoom out).
		//       b. movement to the right is positive (zoom in).
		//   2. Calculate a scale factor for the scene s = 1 + a*dx
		//   3. Trigger any zoom events to change the views.
		// 
		public void ZoomLens( object sender, System.Windows.Forms.MouseEventArgs e )
		{
			float pixel_diff = e.X - lastMousePoint.x; 
			float zoomAmount = 1.0f + pixel_diff * zoomScale;
			zoomAmount = zoomAmount < 0.001f ? 0.001f : zoomAmount;
			//
			// Trigger the zoom event
			//
			if( zoom != null )
			{
				zoom( zoomAmount );
			}
			//
			// Save the zoom factor for the resize event.
			//
			zoomFactor *= zoomAmount;
			//
			// Save the location of the current point for the next movement. 
			//
			lastMousePoint.x = (float) e.X; lastMousePoint.y = (float) e.Y;
        }
        #endregion

        #region Keyboard controllers
        #region Keyboard controller Properties
        static public Vector3 XAxis = new Vector3(1.0f, 0.0f, 0.0f);
        static public Vector3 YAxis = new Vector3(0.0f, 1.0f, 0.0f);
        static public Vector3 ZAxis = new Vector3(0.0f, 0.0f, 1.0f);
		private float rotationIncrementStandard =  0.1f; // radians
		private float rotationIncrementLarge    =  0.4f; // radians
		private float rotationIncrementSmall    =  0.01f; // radians
		private char rotateUpKey = 'w';
		private char rotateDownKey = 's';
		private char rotateLeftKey = 'a';
		private char rotateRightKey = 'd';
		private char rotateUpKeyLarge = 'W';
		private char rotateDownKeyLarge = 'S';
		private char rotateLeftKeyLarge = 'A';
		private char rotateRightKeyLarge = 'D';

	
		public float RotationIncrementStandard
		{
			get{ return rotationIncrementStandard; }
			set{ rotationIncrementStandard = RotationIncrementStandard;}
		}
		public float RotationIncrementLarge
		{
			get{ return rotationIncrementLarge; }
			set{ rotationIncrementLarge = RotationIncrementLarge;}
		}
		public float RotationIncrementSmall
		{
			get{ return rotationIncrementSmall; }
			set{ rotationIncrementSmall = RotationIncrementSmall;}
		}
		public char RotateUpKey
		{
			get{ return rotateUpKey; }
			set
			{ 
				rotateUpKey = char.ToLower( RotateUpKey );
				rotateUpKeyLarge = char.ToUpper( rotateUpKey );
			}
		}
		public char RotateDownKey
		{
			get{ return rotateDownKey; }
			set
			{ 
				rotateDownKey = char.ToLower( RotateDownKey );
				rotateDownKeyLarge = char.ToUpper( rotateDownKey );
			}
		}
		public char RotateLeftKey
		{
			get{ return rotateLeftKey; }
			set
			{ 
				rotateLeftKey = char.ToLower( RotateLeftKey );
				rotateLeftKeyLarge = char.ToUpper( rotateLeftKey );
			}
		}
		public char RotateRightKey
		{
			get{ return rotateRightKey; }
			set
			{ 
				rotateRightKey = char.ToLower( RotateRightKey );
				rotateRightKeyLarge = char.ToUpper( rotateRightKey );
			}
		}
		#endregion

		public void KeyTrackball( object sender, System.Windows.Forms.KeyPressEventArgs e )
		{
            System.Windows.Forms.Control currentCtrl = (System.Windows.Forms.Control)sender;
			//
			// This provides a simple mapping of the keyboard to rotations of the
			// object (or the world if used in a camera).
			// The SHIFT key can be used for gross changes, the CTRL key can be
			// used for fine-control.
			//
            Vector3 rotationAxis = XAxis;
			float sign = 1.0f;
			float rotationAngle = RotationIncrementStandard;

			if( e.KeyChar == rotateUpKey )
			{
				rotationAxis = XAxis;
				sign = -1.0f;
			}
			else if( e.KeyChar == rotateDownKey )
			{
				rotationAxis = XAxis;
			}
			else if( e.KeyChar == rotateLeftKey )
			{
				rotationAxis = YAxis;
				sign = -1.0f;
			}
			else if( e.KeyChar == rotateRightKey )
			{
				rotationAxis = YAxis;
			}
			else if( e.KeyChar == rotateUpKeyLarge )
			{
				rotationAxis = XAxis;
				sign = -1.0f;
				rotationAngle = RotationIncrementLarge;
			}
			else if( e.KeyChar == rotateDownKeyLarge )
			{
				rotationAxis = XAxis;
				rotationAngle = RotationIncrementLarge;
			}
			else if( e.KeyChar == rotateLeftKeyLarge )
			{
				rotationAxis = YAxis;
				sign = -1.0f;
				rotationAngle = RotationIncrementLarge;
			}
			else if( e.KeyChar == rotateRightKeyLarge )
			{
				rotationAxis = YAxis;
				rotationAngle = RotationIncrementLarge;
			}
			
			else
			{
				return;
			}
			//
			// AARRGGHHHH!!!! KeyPressEvent does not support the Modifiers property.
			//
//			float rotationAngle = RotationIncrementStandard;
//			if( e.Modifiers == Keys.Shift )
//				rotationAngle = RotationIncrementLarge;
//			else if( e.Modifiers == Keys.Control )
//				rotationAngle = RotationIncrementSmall;

			if( rotate != null )
			{
				rotate( sign*rotationAngle, rotationAxis );
			}
		}
		public void KeyTrackballArrows( object sender, System.Windows.Forms.KeyEventArgs e )
		{
			//
			// This provides a simple mapping of the keyboard to rotations of the
			// obejct (or the world if used in a camera).
			// The SHIFT key can be used for gross changes, the CTRL key can be
			// used for fine-control.
			//
			// The SHIFT key is not signalled when the keypad keys are pressed.
			//
            Vector3 rotationAxis = XAxis;
			float sign = 1.0f;
			if( e.KeyCode == Keys.Up )
			{
				rotationAxis = XAxis;
				sign = -1.0f;
			}
			else if( e.KeyCode == Keys.Down )
			{
				rotationAxis = XAxis;
			}
			else if( e.KeyCode == Keys.Left )
			{
				rotationAxis = YAxis;
				sign = -1.0f;
			}
			else if( e.KeyCode == Keys.Right )
			{
				rotationAxis = YAxis;
			}
			else
			{
				return;
			}

			float rotationAngle = RotationIncrementStandard;
			if( e.Modifiers == Keys.Shift )
				rotationAngle = RotationIncrementLarge;
			else if( e.Modifiers == Keys.Control )
				rotationAngle = RotationIncrementSmall;

			if( rotate != null )
			{
				rotate( sign*rotationAngle, rotationAxis );
			}
		}
		public void KeyTrackballNumPad( object sender, System.Windows.Forms.KeyEventArgs e )
		{
			//
			// This provides a simple mapping of the keyboard to rotations of the
			// obejct (or the world if used in a camera).
			// The SHIFT key can be used for gross changes, the CTRL key can be
			// used for fine-control.
			//
			// The SHIFT key is not signalled when the keypad keys are pressed.
			//
            Vector3 rotationAxis = XAxis;
			float sign = 1.0f;
			if( e.KeyCode == Keys.NumPad8 )
			{
				rotationAxis = XAxis;
				sign = -1.0f;
			}
			else if( e.KeyCode == Keys.NumPad2 )
			{
				rotationAxis = XAxis;
			}
			else if( e.KeyCode == Keys.NumPad4 )
			{
				rotationAxis = YAxis;
				sign = -1.0f;
			}
			else if( e.KeyCode == Keys.NumPad6 )
			{
				rotationAxis = YAxis;
			}
			else
			{
				return;
			}

			float rotationAngle = RotationIncrementStandard;
			if( e.Modifiers == Keys.Shift )
				rotationAngle = RotationIncrementLarge;
			else if( e.Modifiers == Keys.ControlKey )
				rotationAngle = RotationIncrementSmall;

			if( rotate != null )
			{
				rotate( sign*rotationAngle, rotationAxis );
			}
		}
		#endregion Keyboard controllers
	}
}